using System;
using System.Collections.Generic;
using System.Text;

namespace LSO
{
    /// <summary>
    /// A complete LSO assembly that could be exported to a byte array.
    /// </summary>
    public class LSOAssembly
    {
        /// <summary>
        /// Header
        /// </summary>
        public class HeaderBlock
        {
            /// <summary>
            /// Top of memory
            /// </summary>
            public int TM = 0x00004000;
            /// <summary>
            /// Instruction pointer
            /// </summary>
            public int IP = 0x00000000;
            /// <summary>
            /// Version number
            /// </summary>
            public int VN = 0x00000200;
            /// <summary>
            /// Base pointer
            /// </summary>
            public int BP = 0x00003FFF;
            /// <summary>
            /// Stack pointer
            /// </summary>
            public int SP = 0x00003FFF;
            /// <summary>
            /// Pointer to start of heap
            /// </summary>
            public int HR;
            /// <summary>
            /// Pointer to end of heap
            /// </summary>
            public int HP;
            /// <summary>
            /// Current state
            /// </summary>
            public int CS = 0;
            /// <summary>
            /// Next state
            /// </summary>
            public int NS = 0;
            /// <summary>
            /// Current events waiting to be handled
            /// </summary>
            public int CE = 0;
            /// <summary>
            /// Event currently being handled
            /// </summary>
            public int IE = 0;
            /// <summary>
            /// Events we have active handlers for
            /// </summary>
            public int ER = 0x00000000;
            /// <summary>
            /// Fault register
            /// </summary>
            public int FR = 0x00000000;
            /// <summary>
            /// Sleep register
            /// </summary>
            public int SLR = 0;
            /// <summary>
            /// Pointer to global variables
            /// </summary>
            public int GVR;
            /// <summary>
            /// Pointer to functions
            /// </summary>
            public int GFR;
            /// <summary>
            /// Start parameter
            /// </summary>
            public int PR = 0;
            /// <summary>
            /// Energy supply register
            /// </summary>
            public int ESR = 0;
            /// <summary>
            /// Pointer to states
            /// </summary>
            public int SR;
            /// <summary>
            /// Events waiting to be handled
            /// </summary>
            public long NCE;
            /// <summary>
            /// Event currently being handled
            /// </summary>
            public long NIE = 0x0000000000000000;
            /// <summary>
            /// Events we have active handlers for
            /// </summary>
            public long NER;

            public int Size
            {
                get
                {
                    return 100;
                }
            }

            public void FromBytes(byte[] b)
            {
                if (b.Length < 100)
                    throw new Exception("Received less than 100 bytes for header");
                byte[] buffer = new byte[4];
                Buffer.BlockCopy(b, 0, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                TM = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 4, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                IP = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 8, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                VN = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 12, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                BP = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 16, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                SP = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 20, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                HR = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 24, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                HP = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 28, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                CS = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 32, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                NS = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 36, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                CE = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 40, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                IE = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 44, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                ER = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 48, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                FR = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 52, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                SLR = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 56, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                GVR = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 60, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                GFR = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 64, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                PR = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 68, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                ESR = BitConverter.ToInt32(buffer, 0);
                Buffer.BlockCopy(b, 72, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                SR = BitConverter.ToInt32(buffer, 0);
                buffer = new byte[8];
                Buffer.BlockCopy(b, 76, buffer, 0, 8);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 8);
                NCE = BitConverter.ToInt64(buffer, 0);
                Buffer.BlockCopy(b, 84, buffer, 0, 8);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 8);
                NIE = BitConverter.ToInt64(buffer, 0);
                Buffer.BlockCopy(b, 92, buffer, 0, 8);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 8);
                NER = BitConverter.ToInt64(buffer, 0);
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[100];
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(TM)), 0, b, 0, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(IP)), 0, b, 4, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(VN)), 0, b, 8, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(BP)), 0, b, 12, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(SP)), 0, b, 16, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(HR)), 0, b, 20, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(HP)), 0, b, 24, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(CS)), 0, b, 28, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(NS)), 0, b, 32, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(CE)), 0, b, 36, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(IE)), 0, b, 40, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(ER)), 0, b, 44, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(FR)), 0, b, 48, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(SLR)), 0, b, 52, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(GVR)), 0, b, 56, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(GFR)), 0, b, 60, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(PR)), 0, b, 64, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(ESR)), 0, b, 68, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(SR)), 0, b, 72, 4);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(NCE)), 0, b, 76, 8);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(NIE)), 0, b, 84, 8);
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(NER)), 0, b, 92, 8);
                return b;
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(" ==== HeaderBlock ==== ");
                sb.AppendLine("TM: " + TM.ToString());
                sb.AppendLine("IP: " + IP.ToString());
                sb.AppendLine("VN: " + VN.ToString());
                sb.AppendLine("BP: " + BP.ToString());
                sb.AppendLine("SP: " + SP.ToString());
                sb.AppendLine("HR: " + HR.ToString());
                sb.AppendLine("HP: " + HP.ToString());
                sb.AppendLine("CS: " + CS.ToString());
                sb.AppendLine("NS: " + NS.ToString());
                sb.AppendLine("CE: " + CE.ToString());
                sb.AppendLine("IE: " + IE.ToString());
                sb.AppendLine("ER: " + ER.ToString());
                sb.AppendLine("FR: " + FR.ToString());
                sb.AppendLine("SLR: " + SLR.ToString());
                sb.AppendLine("GVR: " + GVR.ToString());
                sb.AppendLine("GFR: " + GFR.ToString());
                sb.AppendLine("PR: " + PR.ToString());
                sb.AppendLine("ESR: " + ESR.ToString());
                sb.AppendLine("SR: " + SR.ToString());
                sb.AppendLine("NCE: " + NCE.ToString());
                sb.AppendLine("NIE: " + NIE.ToString());
                sb.AppendLine("NER: " + NER.ToString());
                return sb.ToString();
            }
        }

        /// <summary>
        /// A global variable's header and data
        /// </summary>
        public class StaticBlock
        {
            /// <summary>
            /// Contains the size of the header, a type, and possibly a string
            /// </summary>
            public class StaticBlockHeader
            {
                public VariableType TypeOfObject;
                public string StringData;

                public int Size
                {
                    get
                    {
                        return 6 + StringData.Length;
                    }
                }

                public StaticBlockHeader()
                {
                    TypeOfObject = new VariableType();
                    StringData = "";
                }

                public void FromBytes(byte[] b)
                {
                    if (b.Length < 5)
                        throw new Exception("Need at least 5 bytes for StaticBlockHeader");
                    byte[] buffer = new byte[4];
                    Buffer.BlockCopy(b, 0, buffer, 0, 4);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer, 0, 4);
                    int len = BitConverter.ToInt32(buffer, 0) - 5;
                    TypeOfObject = (VariableType)b[4];
                    buffer = new byte[len];
                    Buffer.BlockCopy(b, 5, buffer, 0, len);
                    StringData = Encoding.ASCII.GetString(buffer);
                    if (StringData.EndsWith("\0"))
                        StringData = StringData.Substring(0, StringData.Length - 1);
                }

                public byte[] ToBytes()
                {
                    byte[] b = new byte[Size];
                    Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(Size)), 0, b, 0, 4);
                    b[4] = (byte)TypeOfObject;
                    Buffer.BlockCopy(Encoding.ASCII.GetBytes(StringData), 0, b, 5, StringData.Length);
                    b[Size - 1] = (byte)0;
                    return b;
                }

                public override string ToString()
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(" --- StaticBlockHeader --- ");
                    sb.AppendLine("Size of header: " + Size.ToString());
                    sb.AppendLine("Type of object: " + TypeOfObject.ToString());
                    sb.AppendLine("StringData: " + StringData);
                    return sb.ToString();
                }
            }

            public StaticBlockHeader Header;
            public byte[] Data;

            public StaticBlock()
            {
                Header = new StaticBlockHeader();
            }

            public StaticBlock(int i)
            {
                Header = new StaticBlockHeader();
                Header.TypeOfObject = VariableType.Integer;
                Header.StringData = "";
                Data = new byte[4];
                Data = BitConverter.GetBytes(i);
            }

            public int Size
            {
                get
                {
                    return Header.Size + Data.Length;
                }
            }

            public void FromBytes(byte[] b)
            {
                Header.FromBytes(b);
                int datasize = 0;
                switch (Header.TypeOfObject)
                {
                    case VariableType.Integer:
                        datasize = 4;
                        break;
                    case VariableType.Float:
                        datasize = 4;
                        break;
                    case VariableType.String:
                        datasize = 4;
                        break;
                    case VariableType.Key:
                        datasize = 4;
                        break;
                    case VariableType.Vector:
                        datasize = 12;
                        break;
                    case VariableType.Rotation:
                        datasize = 16;
                        break;
                    case VariableType.List:
                        datasize = 4;
                        break;
                    default:
                        break;
                }
                Data = new byte[datasize];
                Buffer.BlockCopy(b, Header.Size, Data, 0, datasize);
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[Size];
                Buffer.BlockCopy(Header.ToBytes(), 0, b, 0, Header.Size);
                Buffer.BlockCopy(Data, 0, b, Header.Size, Data.Length);
                return b;
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(" ==== StaticBlock ==== ");
                sb.Append(Header.ToString());
                sb.AppendLine(" --- Data --- ");
                sb.AppendLine(BitConverter.ToString(Data));
                return sb.ToString();
            }
        }

        /// <summary>
        /// Indicates the number of and locations of user functions
        /// </summary>
        public class FunctionBlock
        {
            public int NumberOfFunctions = 0;
            /// <summary>
            /// Pointer to each function relative to function block
            /// </summary>
            public List<int> FunctionPointers = new List<int>();

            public int Size
            {
                get
                {
                    return 4 + (FunctionPointers.Count * 4);
                }
            }

            public void FromBytes(byte[] b)
            {
                if (b.Length < 4)
                    throw new Exception("Need at least 4 bytes to start a FunctionBlock");
                byte[] buffer = new byte[4];
                Buffer.BlockCopy(b, 0, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                NumberOfFunctions = BitConverter.ToInt32(buffer, 0);
                int offset = 4;
                for(int i = 0; i < NumberOfFunctions; i++)
                {
                    Buffer.BlockCopy(b, offset, buffer, 0, 4);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    FunctionPointers.Add(BitConverter.ToInt32(buffer, 0));
                    offset += 4;
                }
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[Size];
                byte[] buffer = new byte[4];
                buffer = BitConverter.GetBytes(NumberOfFunctions);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                Buffer.BlockCopy(buffer, 0, b, 0, 4);
                int offset = 4;
                foreach (int pointer in FunctionPointers)
                {
                    buffer = BitConverter.GetBytes(pointer);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    Buffer.BlockCopy(buffer, 0, b, offset, 4);
                    offset += 4;
                }
                return b;
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(" ==== FunctionBlock ==== ");
                sb.AppendLine("Number of functions: " + NumberOfFunctions.ToString());
                sb.AppendLine(" --- FunctionPointers --- ");
                for (int i = 0; i < FunctionPointers.Count; i++)
                {
                    sb.AppendLine(i.ToString() + ": " + FunctionPointers[i].ToString());
                }
                return sb.ToString();
            }
        }

        /// <summary>
        /// Code chunk with a header describing the function prototype.
        /// Used for user-defined functions
        /// </summary>
        public class FunctionCodeChunk
        {
            public class FunctionCodeChunkHeader
            {
                public class FunctionArgument
                {
                    public VariableType Type = new VariableType();
                    public string StringData = "";

                    public int Size
                    {
                        get
                        {
                            return 2 + StringData.Length;
                        }
                    }

                    public void FromBytes(byte[] b, int offset)
                    {
                        Type = (VariableType)b[offset];
                        StringData = "";
                        offset++;
                        while(b[offset] != 0)
                        {
                            StringData += Encoding.ASCII.GetString(b, offset, 1);
                            offset++;
                        }
                        // offset++;
                    }

                    public byte[] ToBytes()
                    {
                        byte[] b = new byte[Size];
                        b[0] = (byte)Type;
                        byte[] buffer = new byte[StringData.Length + 1];
                        Buffer.BlockCopy(Encoding.ASCII.GetBytes(StringData), 0, buffer, 0, StringData.Length);
                        buffer[StringData.Length] = 0;
                        Buffer.BlockCopy(buffer, 0, b, 1, buffer.Length);
                        return b;
                    }

                    public override string ToString()
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine(" -- FunctionArgument -- ");
                        sb.AppendLine("Type: " + Type.ToString());
                        sb.AppendLine("StringData: " + StringData);
                        return sb.ToString();
                    }
                }

                public string StringData = "";
                public VariableType ReturnType = new VariableType();
                public List<FunctionArgument> FunctionArguments = new List<FunctionArgument>();

                public int Size
                {
                    get
                    {
                        int size = 4; // For size of header
                        size += StringData.Length + 1; // For string
                        size += 1; // For return type
                        foreach (FunctionArgument arg in FunctionArguments)
                            size += arg.Size;
                        size += 1; // For end marker
                        return size;
                    }
                }

                public void FromBytes(byte[] b)
                {
                    if (b.Length < 5)
                        throw new Exception("Need at least 5 bytes to start a FunctionCodeChunkHeader");
                    int offset = 0;
                    byte[] buffer = new byte[4];
                    Buffer.BlockCopy(b, offset, buffer, 0, 4);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    int size = BitConverter.ToInt32(buffer, 0);
                    offset += 4;
                    StringData = "";
                    while (b[offset] != 0)
                    {
                        StringData += Encoding.ASCII.GetString(b, offset, 1);
                        offset++;
                    }
                    offset++;
                    ReturnType = (VariableType)b[offset];
                    offset++;
                    FunctionArgument fa;
                    while (offset < size)
                    {
                        if (b[offset] == 0)
                            break; // end marker
                        fa = new FunctionArgument();
                        fa.FromBytes(b, offset);
                        offset += fa.Size;
                        FunctionArguments.Add(fa);
                    }
                }

                public byte[] ToBytes()
                {
                    byte[] b = new byte[Size];
                    int offset = 0;
                    byte[] buffer = BitConverter.GetBytes(Size);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    Buffer.BlockCopy(buffer, 0, b, 0, 4);
                    offset += 4;
                    buffer = Encoding.ASCII.GetBytes(StringData);
                    Buffer.BlockCopy(buffer, 0, b, offset, StringData.Length);
                    offset += StringData.Length;
                    b[offset] = 0;
                    offset++;
                    b[offset] = (byte)ReturnType;
                    offset++;
                    foreach (FunctionArgument arg in FunctionArguments)
                    {
                        buffer = arg.ToBytes();
                        Buffer.BlockCopy(buffer, 0, b, offset, buffer.Length);
                        offset += buffer.Length;
                    }
                    b[Size - 1] = 0;
                    return b;
                }

                public override string ToString()
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(" --- FunctionCodeChunkHeader --- ");
                    sb.AppendLine("StringData: " + StringData);
                    sb.AppendLine("ReturnType: " + ReturnType.ToString());
                    foreach(FunctionArgument arg in FunctionArguments)
                        sb.AppendLine(arg.ToString());
                    return sb.ToString();
                }
            }

            public FunctionCodeChunkHeader Header = new FunctionCodeChunkHeader();
            public byte[] Code = new byte[0];

            public int Size
            {
                get
                {
                    return Header.Size + Code.Length;
                }
            }

            public void FromBytes(byte[] b)
            {
                Header.FromBytes(b);
                Code = new byte[b.Length - Header.Size];
                Buffer.BlockCopy(b, Header.Size, Code, 0, b.Length - Header.Size);
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[Size];
                Buffer.BlockCopy(Header.ToBytes(), 0, b, 0, Header.Size);
                Buffer.BlockCopy(Code, 0, b, Header.Size, Code.Length);
                return b;
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(" ==== FunctionCodeChunk ==== ");
                sb.Append(Header.ToString());
                sb.AppendLine(" --- Code --- ");
                sb.AppendLine(BitConverter.ToString(Code));
                List<LSO.Instruction> disassembled = LSODisassembler.Disassemble(Code);
                foreach (LSO.Instruction instruction in disassembled)
                {
                    sb.AppendLine(instruction.ToString());
                }
                return sb.ToString();
            }
        }

        /// <summary>
        /// Indicates the locations of, event masks for, and number of states
        /// </summary>
        public class StateFrameBlock
        {
            /// <summary>
            /// Pointer (relative to state frame) and event mask for a single state
            /// </summary>
            public class StatePointerBlock
            {
                public int Offset;
                public long EventMask;

                public int Size
                {
                    get
                    {
                        return 12;
                    }
                }

                public void FromBytes(byte[] b)
                {
                    if (b.Length < 12)
                        throw new Exception("Can't create a StatePointerBlock with less than 12 bytes");
                    byte[] buffer = new byte[4];
                    Buffer.BlockCopy(b, 0, buffer, 0, 4);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    Offset = BitConverter.ToInt32(buffer, 0);
                    buffer = new byte[8];
                    Buffer.BlockCopy(b, 4, buffer, 0, 8);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    EventMask = BitConverter.ToInt64(buffer, 0);
                }

                public byte[] ToBytes()
                {
                    byte[] b = new byte[12];
                    Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(Offset)), 0, b, 0, 4);
                    Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(EventMask)), 0, b, 4, 8);
                    return b;
                }

                public override string ToString()
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(" --- StatePointerBlock --- ");
                    sb.AppendLine("Offset: " + Offset.ToString());
                    sb.AppendLine("EventMask: " + EventMask.ToString());
                    return sb.ToString();
                }
            }

            public int NumberOfStates;
            public List<StatePointerBlock> StatePointers = new List<StatePointerBlock>();

            public int Size
            {
                get
                {
                    return 4 + (12 * StatePointers.Count);
                }
            }

            public void FromBytes(byte[] b)
            {
                if (b.Length < 4)
                    throw new Exception("Can't start a StateFrameBlock with less than 4 bytes");
                int offset = 0;
                byte[] buffer = new byte[4];
                Buffer.BlockCopy(b, 0, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                NumberOfStates = BitConverter.ToInt32(buffer, 0);
                StatePointerBlock spb;
                offset += 4;
                buffer = new byte[12];
                for (int i = 0; i < NumberOfStates; i++)
                {
                    Buffer.BlockCopy(b, offset, buffer, 0, 12);
                    spb = new StatePointerBlock();
                    spb.FromBytes(buffer);
                    offset += 12;
                    StatePointers.Add(spb);
                }
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[Size];
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(NumberOfStates)), 0, b, 0, 4);
                int count = StatePointers.Count;
                for (int i = 0; i < count; i++)
                {
                    Buffer.BlockCopy(StatePointers[i].ToBytes(), 0, b, 4 + (i * 12), 12);
                }
                return b;
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(" ==== StateFrameBlock ==== ");
                sb.AppendLine("Number of states: " + NumberOfStates.ToString());
                foreach(StatePointerBlock spb in StatePointers)
                {
                    sb.Append(spb.ToString());
                }
                return sb.ToString();
            }
        }

        /// <summary>
        /// Contains pointers to a state's event handlers
        /// </summary>
        public class StateBlock
        {
            /// <summary>
            /// Apparently just a string
            /// </summary>
            public class StateBlockHeader
            {
                public string StringData;

                public int Size
                {
                    get
                    {
                        return 5 + StringData.Length;
                    }
                }

                public StateBlockHeader()
                {
                    StringData = "";
                }

                public void FromBytes(byte[] b)
                {
                    if (b.Length < 5)
                        throw new Exception("Can't create a StateBlockHeader with less than 5 bytes");
                    byte[] buffer = new byte[4];
                    Buffer.BlockCopy(b, 0, buffer, 0, 4);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    int len = BitConverter.ToInt32(buffer, 0) - 4;
                    buffer = new byte[len];
                    Buffer.BlockCopy(b, 4, buffer, 0, len);
                    StringData = Encoding.ASCII.GetString(buffer);
                    if (StringData.EndsWith("\0"))
                        StringData = StringData.Substring(0, StringData.Length - 1);
                }

                public byte[] ToBytes()
                {
                    byte[] b = new byte[Size];
                    Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(Size)), 0, b, 0, 4);
                    Buffer.BlockCopy(Encoding.ASCII.GetBytes(StringData), 0, b, 4, StringData.Length);
                    b[Size - 1] = (byte)0;
                    return b;
                }

                public override string ToString()
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(" --- StateBlockHeader --- ");
                    sb.AppendLine("Size of header: " + Size.ToString());
                    sb.AppendLine("StringData: " + StringData);
                    return sb.ToString();
                }
            }

            /// <summary>
            /// Points to an event handler (relative to end of state block header)
            /// and indicates its call frame size
            /// </summary>
            public class StateBlockHandler
            {
                public int Pointer;
                public int CallFrameSize;

                public int Size
                {
                    get
                    {
                        return 8;
                    }
                }

                public void FromBytes(byte[] b)
                {
                    if (b.Length < 8)
                        throw new Exception("Can't create a StateBlockHandler with less than 8 bytes");
                    byte[] buffer = new byte[4];
                    Buffer.BlockCopy(b, 0, buffer, 0, 4);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    Pointer = BitConverter.ToInt32(buffer, 0);
                    Buffer.BlockCopy(b, 4, buffer, 0, 4);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    CallFrameSize = BitConverter.ToInt32(buffer, 0);
                }

                public byte[] ToBytes()
                {
                    byte[] b = new byte[Size];
                    Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(Pointer)), 0, b, 0, 4);
                    Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(CallFrameSize)), 0, b, 4, 4);
                    return b;
                }

                public override string ToString()
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(" --- StateBlockHandler --- ");
                    sb.AppendLine("Pointer to code chunk: " + Pointer.ToString());
                    sb.AppendLine("Call frame size: " + CallFrameSize.ToString());
                    return sb.ToString();
                }
            }

            public StateBlockHeader Header = new StateBlockHeader();
            public List<StateBlockHandler> StateBlockHandlers = new List<StateBlockHandler>();

            public int Size
            {
                get
                {
                    return Header.Size + (8 * StateBlockHandlers.Count);
                }
            }

            public void FromBytes(byte[] b, int eventCount)
            {
                int offset = 0;
                Header.FromBytes(b);
                offset += Header.Size;
                byte[] buffer = new byte[8];
                StateBlockHandler sbh;
                for(int i = 0; i < eventCount; i++)
                {
                    Buffer.BlockCopy(b, offset, buffer, 0, 8);
                    sbh = new StateBlockHandler();
                    sbh.FromBytes(buffer);
                    StateBlockHandlers.Add(sbh);
                    offset += 8;
                }
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[Size];
                int handlercount = StateBlockHandlers.Count;
                Buffer.BlockCopy(Header.ToBytes(), 0, b, 0, Header.Size);
                for (int i = 0; i < handlercount; i++)
                {
                    Buffer.BlockCopy(StateBlockHandlers[i].ToBytes(), 0, b, Header.Size + (i * 8), 8);
                }
                return b;
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(" ==== StateBlock ==== ");
                sb.Append(Header.ToString());
                foreach (StateBlockHandler sbh in StateBlockHandlers)
                {
                    sb.Append(sbh.ToString());
                }
                return sb.ToString();
            }
        }

        /// <summary>
        /// Code chunk with a simple header, used for event handlers
        /// </summary>
        public class EventHandlerCodeChunk
        {
            /// <summary>
            /// Just a string
            /// </summary>
            public class EventHandlerCodeChunkHeader
            {
                public string StringData;

                public int Size
                {
                    get
                    {
                        return 5 + StringData.Length;
                    }
                }

                public EventHandlerCodeChunkHeader()
                {
                    StringData = "";
                }

                public void FromBytes(byte[] b)
                {
                    if (b.Length < 5)
                        throw new Exception("Can't create an EventHandlerCodeChunkHeader with less than 5 bytes");
                    byte[] buffer = new byte[4];
                    Buffer.BlockCopy(b, 0, buffer, 0, 4);
                    if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                    int len = BitConverter.ToInt32(buffer, 0) - 4;
                    buffer = new byte[len];
                    Buffer.BlockCopy(b, 4, buffer, 0, len);
                    StringData = Encoding.ASCII.GetString(buffer);
                    if (StringData.EndsWith("\0"))
                        StringData = StringData.Substring(0, StringData.Length - 1);
                }

                public byte[] ToBytes()
                {
                    byte[] b = new byte[Size];
                    Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(Size)), 0, b, 0, 4);
                    Buffer.BlockCopy(Encoding.ASCII.GetBytes(StringData), 0, b, 4, StringData.Length);
                    b[Size - 1] = (byte)0;
                    return b;
                }

                public override string ToString()
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(" --- CodeChunkHeader --- ");
                    sb.AppendLine("Size of header: " + Size.ToString());
                    sb.AppendLine("StringData: " + StringData.Replace("\0", "\\0"));
                    return sb.ToString();
                }
            }

            public EventHandlerCodeChunkHeader Header = new EventHandlerCodeChunkHeader();
            public byte[] Code;

            public int Size
            {
                get
                {
                    return Header.Size + Code.Length;
                }
            }

            public void FromBytes(byte[] b)
            {
                Header.FromBytes(b);
                Code = new byte[b.Length - Header.Size];
                Buffer.BlockCopy(b, Header.Size, Code, 0, b.Length - Header.Size);
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[Size];
                Buffer.BlockCopy(Header.ToBytes(), 0, b, 0, Header.Size);
                Buffer.BlockCopy(Code, 0, b, Header.Size, Code.Length);
                return b;
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(" ==== EventHandlerCodeChunk ==== ");
                sb.Append(Header.ToString());
                sb.AppendLine(" --- Code --- ");
                sb.AppendLine(BitConverter.ToString(Code));
                List<LSO.Instruction> disassembled = LSODisassembler.Disassemble(Code);
                foreach (LSO.Instruction instruction in disassembled)
                {
                    sb.AppendLine(instruction.ToString());
                }
                return sb.ToString();
            }
        }

        public class HeapBlock
        {
            public VariableType TypeOfObject = new VariableType();
            public short ReferenceCount;
            public byte[] Data;

            public int SizeOfDataBlock
            {
                get
                {
                    return Data.Length;
                }
            }

            public int Size
            {
                get
                {
                    return 7 + SizeOfDataBlock;
                }
            }

            public void FromBytes(byte[] b)
            {
                if (b.Length < 4)
                    throw new Exception("Can't create a HeapBlock with less than 4 bytes");
                byte[] buffer = new byte[4];
                Buffer.BlockCopy(b, 0, buffer, 0, 4);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                int datasize = BitConverter.ToInt32(buffer, 0);
                TypeOfObject = (VariableType)b[4];
                buffer = new byte[2];
                Buffer.BlockCopy(b, 5, buffer, 0, 2);
                if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                ReferenceCount = BitConverter.ToInt16(buffer, 0);
                Data = new byte[datasize];
                Buffer.BlockCopy(b, 7, Data, 0, datasize);
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[Size];
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(SizeOfDataBlock)), 0, b, 0, 4);
                b[4] = (byte)TypeOfObject;
                Buffer.BlockCopy(Helpers.BigEndian(BitConverter.GetBytes(ReferenceCount)), 0, b, 5, 2);
                Buffer.BlockCopy(Data, 0, b, 7, SizeOfDataBlock);
                return b;
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(" ==== HeapBlock ==== ");
                sb.AppendLine("Size of data: " + SizeOfDataBlock.ToString());
                sb.AppendLine("Type of object: " + TypeOfObject.ToString());
                sb.AppendLine("Reference count: " + ReferenceCount.ToString());
                sb.AppendLine("Data: " + BitConverter.ToString(Data));
                return sb.ToString();
            }

            public HeapBlock()
            {
                ReferenceCount = 1;
                Data = new byte[0];
            }

            public HeapBlock(VariableType type, byte[] data)
            {
                TypeOfObject = type;
                ReferenceCount = 1;
                Data = data;
            }
        }

        public HeaderBlock Header = new HeaderBlock();
        public List<StaticBlock> StaticBlocks = new List<StaticBlock>();
        public FunctionBlock FunctionsBlock = new FunctionBlock();
        public List<FunctionCodeChunk> FunctionCodeChunks = new List<FunctionCodeChunk>();
        public StateFrameBlock StatesFrameBlock = new StateFrameBlock();
        public List<StateBlock> StateBlocks = new List<StateBlock>();
        public List<EventHandlerCodeChunk> EventHandlerCodeChunks = new List<EventHandlerCodeChunk>();
        public List<HeapBlock> HeapBlocks = new List<HeapBlock>();

        public int Size
        {
            // FIXME: This size doesn't include the heap end marker!
            get
            {
                int size = Header.Size;
                foreach (StaticBlock block in StaticBlocks)
                    size += block.Size;
                size += FunctionsBlock.Size;
                foreach (FunctionCodeChunk block in FunctionCodeChunks)
                    size += block.Size;
                size += StatesFrameBlock.Size;
                foreach (StateBlock block in StateBlocks)
                    size += block.Size;
                foreach (EventHandlerCodeChunk block in EventHandlerCodeChunks)
                    size += block.Size;
                foreach (HeapBlock block in HeapBlocks)
                    size += block.Size;
                return size;
            }
        }

        public byte[] Assembled()
        {
            byte[] b = new byte[16384];
            int length = 0;
            int i, i2;
            int l, l2;
            int StateFrameLocation;
            int StateBlocksLocation;
            List<int> StateBlockFrames = new List<int>();
            List<int> CodeChunkLocations = new List<int>();

            // Reserve space for header
            length += Header.Size;

            // Copy StaticBlocks
            Header.GVR = length;
            foreach (StaticBlock sb in StaticBlocks)
            {
                Buffer.BlockCopy(sb.ToBytes(), 0, b, length, sb.Size);
                length += sb.Size;
            }

            Header.GFR = length;
            if (FunctionsBlock.NumberOfFunctions > 0)
            {
                // Reserve space for FunctionBlock
                length += FunctionsBlock.Size;

                // Copy FunctionCodeChunks
                for (int fi = 0; fi < FunctionsBlock.NumberOfFunctions; fi++)
                {
                    FunctionsBlock.FunctionPointers[fi] = length - Header.GFR;
                    Buffer.BlockCopy(FunctionCodeChunks[fi].ToBytes(), 0, b, length, FunctionCodeChunks[fi].Size);
                    length += FunctionCodeChunks[fi].Size;
                }

                // Copy FunctionBlock
                Buffer.BlockCopy(FunctionsBlock.ToBytes(), 0, b, Header.GFR, FunctionsBlock.Size);
            }

            // Reserve space for StatePointers
            StateFrameLocation = length;
            Header.SR = StateFrameLocation;
            length += StatesFrameBlock.Size;

            // Reserve space for StateBlocks
            StateBlocksLocation = length;
            l = StateBlocks.Count;
            for (i = 0; i < l; i++)
            {
                // Set StatePointer for this StateBlock
                StatesFrameBlock.StatePointers[i].Offset = length - StateFrameLocation;
                // Store location of end of state block header
                StateBlockFrames.Add(length + StateBlocks[i].Header.Size);
                length += StateBlocks[i].Size;
            }

            // Copy StatesFrameBlock
            Buffer.BlockCopy(StatesFrameBlock.ToBytes(), 0, b, StateFrameLocation, StatesFrameBlock.Size);

            // Copy code chunks
            l = EventHandlerCodeChunks.Count;
            for(i = 0; i < l; i++)
            {
                CodeChunkLocations.Add(length);
                Buffer.BlockCopy(EventHandlerCodeChunks[i].ToBytes(), 0, b, length, EventHandlerCodeChunks[i].Size);
                length += EventHandlerCodeChunks[i].Size;
            }

            // Put code locations in StateBlocks
            int handlerindex = 0;
            int frame;
            int t;
            l = StateBlocks.Count;
            for(i = 0; i < l; i++)
            {
                frame = StateBlockFrames[i];
                l2 = StateBlocks[i].StateBlockHandlers.Count;
                for(i2 = 0; i2 < l2; i2++)
                {
                    t = CodeChunkLocations[handlerindex] - frame;
                    StateBlocks[i].StateBlockHandlers[i2].Pointer = t;
                    handlerindex++;
                }
            }

            // Copy StateBlocks
            i = 0;
            foreach (StateBlock sb in StateBlocks)
            {
                Buffer.BlockCopy(sb.ToBytes(), 0, b, StateBlocksLocation + i, sb.Size);
                i += sb.Size;
            }

            // Copy HeapBlocks
            Header.HR = length;
            foreach (HeapBlock hb in HeapBlocks)
            {
                Buffer.BlockCopy(hb.ToBytes(), 0, b, length, hb.Size);
                length += hb.Size;
            }
            Buffer.BlockCopy(new byte[] { 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00 }, 0, b, length, 7);
            length += 7;
            Header.HP = length;

            // Copy Header
            Buffer.BlockCopy(Header.ToBytes(), 0, b, 0, Header.Size);



            // Return a correctly sized array
            byte[] finished = new byte[length];
            Buffer.BlockCopy(b, 0, finished, 0, length);
            return finished;
        }

        public void FromBytes(byte[] b)
        {
            byte[] buffer;

            if (b[0] == 0 && b[1] == 0x40 && b[2] == 0 && b[3] == 0)
            {
                buffer = new byte[b.Length - 4];
                Buffer.BlockCopy(b, 4, buffer, 0, buffer.Length);
                b = buffer;
            }

            int offset = 0;
            int endoffset;
            int buffersize;
            // Header
            Header.FromBytes(b);
            offset += Header.Size;
            // Static blocks
            offset = Header.GVR;
            if (Header.GVR < Header.GFR)
            {
                buffersize = Header.GFR - Header.GVR;
                int bufferoffset = 0;
                StaticBlock sb;
                while (bufferoffset < buffersize)
                {
                    buffer = new byte[buffersize - bufferoffset];
                    Buffer.BlockCopy(b, offset + bufferoffset, buffer, 0, buffersize - bufferoffset);
                    sb = new StaticBlock();
                    sb.FromBytes(buffer);
                    StaticBlocks.Add(sb);
                    bufferoffset += sb.Size;
                }
            }
            // Function block
            offset = Header.GFR;
            if (Header.GFR < Header.SR)
            {
                buffersize = Header.SR - Header.GFR;
                buffer = new byte[buffersize];
                Buffer.BlockCopy(b, offset, buffer, 0, buffersize);
                FunctionsBlock.FromBytes(buffer);
            }
            // Find location of each FunctionCodeChunk
            List<int> FunctionLocations = new List<int>();
            foreach (int pointer in FunctionsBlock.FunctionPointers)
            {
                FunctionLocations.Add(Header.GFR + pointer);
            }
            // FunctionCodeChunks
            FunctionCodeChunk fchunk;
            foreach (int funcstart in FunctionLocations)
            {
                offset = funcstart;
                endoffset = Header.SR;
                foreach (int location in FunctionLocations)
                {
                    if (location > offset && location < endoffset)
                        endoffset = location;
                }
                buffersize = endoffset - offset;
                buffer = new byte[buffersize];
                Buffer.BlockCopy(b, offset, buffer, 0, buffersize);
                fchunk = new FunctionCodeChunk();
                fchunk.FromBytes(buffer);
                FunctionCodeChunks.Add(fchunk);
            }
            // State frame block
            offset = Header.SR;
            buffer = new byte[b.Length - offset];
            Buffer.BlockCopy(b, offset, buffer, 0, b.Length - offset);
            StatesFrameBlock.FromBytes(buffer);

            List<int> CodeChunkLocations = new List<int>();
            // State blocks
            List<int> StateBlockFrames = new List<int>();
            StateBlock newstate;
            int eventcount;
            foreach (StateFrameBlock.StatePointerBlock spb in StatesFrameBlock.StatePointers)
            {
                offset = spb.Offset + Header.SR;
                buffersize = b.Length - offset;
                buffer = new byte[buffersize];
                Buffer.BlockCopy(b, offset, buffer, 0, buffersize);
                eventcount = 0;
                for (int ie = 0; ie < 64; ie++)
                {
                    if ((spb.EventMask & ((long)1 << ie)) != 0)
                        eventcount++;
                }
                newstate = new StateBlock();
                newstate.FromBytes(buffer, eventcount);
                StateBlocks.Add(newstate);
                StateBlockFrames.Add(offset + newstate.Header.Size);
                CodeChunkLocations.Add(offset); // Oops!
            }
            // Find location for each code chunk
            int stateindex = 0;
            foreach (StateBlock state in StateBlocks)
            {
                foreach (StateBlock.StateBlockHandler handler in state.StateBlockHandlers)
                {
                    CodeChunkLocations.Add(StateBlockFrames[stateindex] + handler.Pointer);
                }
                stateindex++;
            }
            // Add code chunks
            stateindex = 0;
            EventHandlerCodeChunk chunk;
            foreach (StateBlock state in StateBlocks)
            {
                foreach (StateBlock.StateBlockHandler handler in state.StateBlockHandlers)
                {
                    offset = StateBlockFrames[stateindex] + handler.Pointer;
                    endoffset = Header.HR;
                    foreach (int location in CodeChunkLocations)
                    {
                        if (location > offset && location < endoffset)
                            endoffset = location;
                    }
                    buffersize = endoffset - offset;
                    buffer = new byte[buffersize];
                    Buffer.BlockCopy(b, offset, buffer, 0, buffersize);
                    chunk = new EventHandlerCodeChunk();
                    chunk.FromBytes(buffer);
                    EventHandlerCodeChunks.Add(chunk);
                }
                stateindex++;
            }
            // Heap blocks
            HeapBlock hb;
            offset = Header.HR;
            int heapend = Header.HP;
            if (b.Length < heapend)
                heapend = b.Length;
            while ((offset + 7) < heapend)
            {
                buffer = new byte[heapend - offset];
                Buffer.BlockCopy(b, offset, buffer, 0, heapend - offset);
                hb = new HeapBlock();
                hb.FromBytes(buffer);
                HeapBlocks.Add(hb);
                offset += hb.Size;
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(" ======== LSOAssembly ======== ");
            sb.AppendLine(Header.ToString());
            foreach (StaticBlock b in StaticBlocks)
                sb.AppendLine(b.ToString());
            if (FunctionsBlock.NumberOfFunctions > 0)
                sb.AppendLine(FunctionsBlock.ToString());
            foreach (FunctionCodeChunk fcc in FunctionCodeChunks)
                sb.AppendLine(fcc.ToString());
            sb.AppendLine(StatesFrameBlock.ToString());
            foreach (StateBlock b in StateBlocks)
                sb.AppendLine(b.ToString());
            foreach (EventHandlerCodeChunk b in EventHandlerCodeChunks)
                sb.AppendLine(b.ToString());
            foreach (HeapBlock b in HeapBlocks)
                sb.AppendLine(b.ToString());
            return sb.ToString();
        }
    }
}
